package io.gitee.zhangbinhub.admin.workflow.service

import io.gitee.zhangbinhub.acp.boot.component.FileDownLoadHandle
import io.gitee.zhangbinhub.acp.boot.exceptions.WebException
import io.gitee.zhangbinhub.acp.core.common.CommonTools
import io.gitee.zhangbinhub.admin.common.base.BaseService
import io.gitee.zhangbinhub.admin.common.vo.CustomerQueryPageVo
import io.gitee.zhangbinhub.admin.resource.server.vo.ProcessDefinitionVo
import io.gitee.zhangbinhub.admin.workflow.constant.WorkFlowConstant
import io.gitee.zhangbinhub.admin.workflow.entity.WorkFlowDefinition
import io.gitee.zhangbinhub.admin.workflow.po.WorkFlowDefinitionPo
import io.gitee.zhangbinhub.admin.workflow.po.WorkFlowDefinitionQueryPo
import io.gitee.zhangbinhub.admin.workflow.repo.WorkFlowDefinitionRepository
import jakarta.persistence.criteria.Predicate
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.camunda.bpm.engine.RepositoryService
import org.camunda.bpm.model.bpmn.Bpmn
import org.camunda.bpm.model.bpmn.instance.Process
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.domain.Page
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.io.ByteArrayInputStream

/**
 * @author zhang by 11/01/2019
 * @since JDK 11
 */
@Service
@Transactional(readOnly = true)
class WorkFlowDefinitionService @Autowired constructor(
    private val repositoryService: RepositoryService,
    private val workFlowDefinitionRepository: WorkFlowDefinitionRepository,
    private val fileDownLoadHandle: FileDownLoadHandle
) : BaseService() {

    @Throws(WebException::class)
    private fun parseProcessDefinition(content: String): ProcessDefinitionVo = try {
        Bpmn.readModelFromStream(ByteArrayInputStream(content.toByteArray(CommonTools.getDefaultCharset())))
            .let { modelInstance ->
                val processType = modelInstance.model.getType(Process::class.java)
                val elementList = modelInstance.getModelElementsByType(processType)
                val modelElementInstance = elementList.first()
                val id = modelElementInstance.getAttributeValue("id")
                val name = modelElementInstance.getAttributeValue("name")
                if (CommonTools.isNullStr(id)) {
                    throw WebException("没有配置流程id")
                }
                if (CommonTools.isNullStr(name)) {
                    throw WebException("没有配置流程名称")
                }
                ProcessDefinitionVo(id, name, content)
            }
    } catch (e: Exception) {
        throw WebException(e.message)
    }

    @Transactional
    @Throws(WebException::class)
    fun doDeploy(id: String): WorkFlowDefinition = workFlowDefinitionRepository.getReferenceById(id).apply {
        if (this.deployTime != null) {
            throw WebException("流程【${this.name}】版本【${this.version}】已部署，不能重复部署！")
        }
        val deployment = repositoryService.createDeployment().name(name).addInputStream(
            "${name}_${version}${WorkFlowConstant.resourceSuffix}",
            ByteArrayInputStream(content.toByteArray(CommonTools.getDefaultCharset()))
        ).deploy() ?: throw WebException("流程部署失败！")
        deployTime = deployment.deploymentTime.time
        deploymentId = deployment.id
    }.let {
        workFlowDefinitionRepository.save(it)
    }

    @Transactional
    @Throws(WebException::class)
    fun doCreate(workFlowDefinitionPo: WorkFlowDefinitionPo): WorkFlowDefinition =
        parseProcessDefinition(workFlowDefinitionPo.content!!).let { processDefinitionVo ->
            WorkFlowDefinition(
                processKey = processDefinitionVo.id!!,
                name = processDefinitionVo.name!!,
                version = (workFlowDefinitionRepository.findAllByProcessKeyOrderByVersionDesc(processDefinitionVo.id!!)
                    .maxOfOrNull { it.version } ?: 0) + 1,
                remarks = workFlowDefinitionPo.remarks,
                content = workFlowDefinitionPo.content!!,
                enabled = true
            ).let {
                workFlowDefinitionRepository.save(it)
            }
        }

    @Transactional
    @Throws(WebException::class)
    fun doUpdate(workFlowDefinitionPo: WorkFlowDefinitionPo): WorkFlowDefinition =
        workFlowDefinitionRepository.getReferenceById(workFlowDefinitionPo.id!!).apply {
            if (this.deployTime == null) {
                val processDefinitionVo = parseProcessDefinition(workFlowDefinitionPo.content!!)
                this.processKey = processDefinitionVo.id!!
                this.name = processDefinitionVo.name!!
                this.remarks = workFlowDefinitionPo.remarks
                this.content = workFlowDefinitionPo.content!!
                this.modifyTime = System.currentTimeMillis()
            }
            this.remarks = workFlowDefinitionPo.remarks
        }.let {
            workFlowDefinitionRepository.save(it)
        }

    @Transactional
    fun doDelete(idList: MutableList<String>) =
        workFlowDefinitionRepository.findAllById(idList).forEach { workFlowDefinition ->
            if (workFlowDefinition.deployTime != null) {
                workFlowDefinitionRepository.save(workFlowDefinition.apply {
                    this.enabled = false
                })
            } else {
                workFlowDefinitionRepository.delete(workFlowDefinition)
            }
        }

    fun doQuery(workFlowDefinitionQueryPo: WorkFlowDefinitionQueryPo): CustomerQueryPageVo<WorkFlowDefinition> =
        workFlowDefinitionRepository.findAll({ root, _, criteriaBuilder ->
            val predicateList: MutableList<Predicate> = mutableListOf()
            predicateList.add(criteriaBuilder.equal(root.get<Boolean>("enabled"), true))
            if (!CommonTools.isNullStr(workFlowDefinitionQueryPo.name)) {
                predicateList.add(
                    criteriaBuilder.like(
                        root.get<Any>("name").`as`(String::class.java), "%" + workFlowDefinitionQueryPo.name + "%"
                    )
                )
            }
            if (!CommonTools.isNullStr(workFlowDefinitionQueryPo.processKey)) {
                predicateList.add(
                    criteriaBuilder.like(
                        root.get<Any>("processKey").`as`(String::class.java),
                        "%" + workFlowDefinitionQueryPo.processKey + "%"
                    )
                )
            }
            criteriaBuilder.and(*predicateList.toTypedArray())
        }, buildPageRequest(workFlowDefinitionQueryPo.queryParam!!)).let { page ->
            CustomerQueryPageVo(
                currPage = workFlowDefinitionQueryPo.queryParam!!.currPage!!,
                pageSize = workFlowDefinitionQueryPo.queryParam!!.pageSize!!,
                totalElements = page.totalElements,
                content = page.content
            )
        }

    @Throws(WebException::class)
    fun doDownLoadFile(request: HttpServletRequest, response: HttpServletResponse, id: String) {
        val workFlowDefinition = workFlowDefinitionRepository.getReferenceById(id)
        val targetFile = CommonTools.contentWriteToFile(
            WorkFlowConstant.upLoadTempPath + "/${workFlowDefinition.name}_${workFlowDefinition.version}${WorkFlowConstant.resourceSuffix}",
            workFlowDefinition.content
        ) ?: throw WebException("生成文件失败")
        val foldPath = targetFile.parentFile.canonicalPath
        fileDownLoadHandle.downLoadFile(
            request, response, targetFile.canonicalPath, listOf("$foldPath/.*"), true, 120000
        )
    }
}
